#include "vector.hpp"
#include "shape.hpp"
#include "light.hpp"

#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace std;

#define INF 2147483647

const int Cw = 768;
const int Ch = 768;
const int CHANNELS = 3;

const float Vw = 1.0;
const float Vh = 1.0;
const float d = 1.0;

unsigned char pic[Cw][Ch][CHANNELS] = {
    0,
};

Sphere spheres[] = {
    {
        Vector(0, -1, 3),
        1,
        Color(255, 0, 0),
        500
    },
    {
        Vector(2, 0, 4),
        1,
        Color(0, 0, 255),
        500
    },
    {
        Vector(-2, 0, 4),
        1,
        Color(0, 255, 0),
        10
    }
};

void log(Vector& v) {
    cout << "(" << v.x << ", " << v.y << ", " << v.z << ")" << endl;
}

void save_to_file(const char* filename) {
    FILE* f = fopen(filename, "w");
    fprintf(f, "P3\n%d %d\n%d\n", Cw, Ch, 255);
    for (int i = 0; i < Cw; i++) {
        for (int j = 0; j < Ch; j++) {
            fprintf(f, "%d %d %d ", pic[i][j][0], pic[i][j][1], pic[i][j][2]);
        }
    }
    fclose(f);
}

Vector canvasToViewPort(int x, int y) {
    return Vector(x * Vw / Cw, y * Vh / Ch, d);
}

void putPixel(int row, int line, Color& c) {
    row = Ch / 2 - row - 1;
    line += Cw / 2;
    pic[row][line][0] = c.x;
    pic[row][line][1] = c.y;
    pic[row][line][2] = c.z;
}

void intersectRaySphere(const Vector origin, const Vector point,
    const Sphere& sphere, float& t1, float t2) {
    float r = sphere.radius;
    Vector co = origin - sphere.center;

    float a = point.dot(point);
    float b = 2 * co.dot(point);
    float c = co.dot(co) - r * r;

    float delta = b * b - 4 * a * c;
    if (delta < 0) {
        t1 = INF;
        t2 = INF;
    }
    else {
        t1 = (-b + sqrt(delta)) / (2.0 * a);
        t2 = (-b - sqrt(delta)) / (2.0 * a);
        //printf("%f, %f, %f, %f\n", a, b, c, t1);
    }
}

float computeLighting(const Vector& point, const Vector& N, 
    Vector&& V, float specular) {
    float li = 0.0;
    for (int i = 0; i < LIGHT_NUM; i++) {
        if (lights[i].light_type == LightType::AMBIENT) {
            li += lights[i].intensity;
        }
        else {
            Vector light_vec(0, 0, 0);
            if (lights[i].light_type == LightType::DIRECTIONAL) {
                light_vec = lights[i].position; // 这里实际上是平行光的方向
            }
            else if (lights[i].light_type == LightType::POINT) {
                light_vec = lights[i].position - point;
            }

            float n_dot_l = N.dot(light_vec);
            if (n_dot_l > 0) {
                // 因为 N 是单位法向量，这里就不再除以 N 的长度了。
                li += lights[i].intensity * n_dot_l / light_vec.length();
            }

            if (specular > 0) {
                Vector R = N * (2 * N.dot(light_vec)) - light_vec;
                float r_dot_v = R.dot(V);
                if (r_dot_v > 0) {
                    li += lights[i].intensity * pow(r_dot_v / (R.length() * V.length()),
                        specular);
                }
            }
        }
    }

    if (li > 1.0) {
        li = 1.0;
    }

    return li;
}

Color TraceRay(const Vector& origin, const Vector& canvas_point, 
    const float front_plane, const float back_plane) {
    float closest_t = INF;
    Sphere* closest_sphere = nullptr;

    for (int i = 0; i < sizeof(spheres) / sizeof(Sphere); i++) {
        float t1 = 0;
        float t2 = 0;
        intersectRaySphere(origin, canvas_point, spheres[i], t1, t2);
        if (t1 >= front_plane && t1 < back_plane && t1 < closest_t) {
            closest_t = t1;
            closest_sphere = spheres + i;
        }
        
        if (t2 >= front_plane && t2 < back_plane && t2 < closest_t) {
            closest_t = t2;
            closest_sphere = spheres + i;
        }
    }

    if (closest_sphere) {
        Vector point = origin + ((canvas_point - origin) * closest_t);
        Vector N = point - closest_sphere->center;
        N.normalize();
        return closest_sphere->color * computeLighting(point, N, point - origin, closest_sphere->specular);
    }
    else {
        return Color(0, 0, 0);
    }
}

int main() {
    Vector o(0, 0, 0);

    for (int y = -Ch / 2; y < Ch / 2; y++) {
        for (int x = -Cw / 2; x < Cw / 2; x++) {
            Vector D = canvasToViewPort(x, y);
            Color color = TraceRay(o, D, 1, 100);
            putPixel(y, x, color);
        }
    }

    save_to_file("test.ppm");
    return 0;
}